package Game.Objects;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Vector2f;

import Game.GameHelper;
import Game.Level;
import Game.MapBlock;
import Game.MapBlock.EMapBlockType;
import Game.Objects.Creatures.GOCreature;
import GameMath.GPoint;
import GameMath.GSize;

/** Object manager class
 * @author Aikidos
 */
public class ObjectManager {

	/** Level */
	private Level _level;
	
	/** Object creator */
	private ObjectCreator _objectCreator;
	
	/** List of game object */
	private List<GameObject> _lstObjects = new ArrayList<GameObject>();
	
	/** Camera */
	private GOCamera _camera;
	
	/** Player */
	private GOPlayer _player;
	
	/** Initialize object manager
	 * @param level - Level
	 */
	public void init(Level level) throws SlickException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
		_level = level;
		
		// Create camera
		_camera = new GOCamera(this);
		
		// Create player
		_player = new GOPlayer(this);
		
		// Remove camera and player from list
		_lstObjects.remove(_camera);
		_lstObjects.remove(_player);
		
		// Initialize object creator
		_objectCreator = new ObjectCreator(this);
		_objectCreator.init();
	}
	
	/** Returns list of object
	 * @return List of object
	 */
	public List<GameObject> getObjects() {
		return _lstObjects;
	}
	
	/** Returns player
	 * @return Player
	 */
	public GOPlayer getPlayer() {
		return _player;
	}
	
	/** Returns camera
	 * @return Camera
	 */
	public GOCamera getCamera() {
		return _camera;
	}

	/** Returns level
	 * @return Level
	 */
	public Level getLevel() {
		return _level;
	}
	
	/** Returns object creator
	 * @return Object creator
	 */
	public ObjectCreator getObjectCreator() {
		return _objectCreator;
	}
	
	/** Add new object
	 * @param gameObject - Game object
	 */
	public void addObject(GameObject gameObject) {
		_lstObjects.add(gameObject);
	}
	
	/** Delete all objects */
	public void deleteAllObjects() {
		_lstObjects.clear();
	}
	
	/** Destroy object
	 * @param object - Game object
	 */
	public void destroyObject(GameObject object) {
		object.destroy();
		_lstObjects.remove(object);
	}
	
	/** Spawn object
	 * @param gameObject - Game object
	 * @param banCoords - Banned spawn coordinates
	 */
	public void spawnObject(GameObject gameObject, List<GPoint> banCoords) {
		Random rnd = GameHelper.getRandom();
		
		// Get map
		MapBlock[][] mapBlocks = _level.getMap().getBlocks();
		
		// Get map size
		GSize mapSize = _level.getMap().getSize();
		
		// Create spawn coordinate
		GPoint spawnPos = null;
		
		while (true) {
			spawnPos = new GPoint(rnd.nextInt(mapSize.width),
					rnd.nextInt(mapSize.width));
			
			boolean isBan = false;
			for (GPoint p : banCoords) {
				if ((spawnPos.x == p.x) && (spawnPos.y == p.y)) isBan = true;
			}
			
			if (isBan) continue;
			
			if (mapBlocks[spawnPos.x][spawnPos.y].Type != EMapBlockType.Floor) continue;
			
			break;
		}

		gameObject.Position = coordToObjectPos(spawnPos);
	}
	
	/** Returns object coordinates
	 * @param gameObject - Game object
	 * @return Object coordinates
	 */
	public GPoint getObjectCoord(GameObject gameObject) {
		Vector2f pos = gameObject.Position;

		GPoint result = new GPoint((int) (pos.x - (Level.BLOCK_SIZE / 2)),
				(int) (pos.y - (Level.BLOCK_SIZE / 2)));
		result.x /= Level.BLOCK_SIZE;
		result.y /= Level.BLOCK_SIZE;

		return result;
	}
	
	/** Convert map coordinate to object position
	 * @param coord - Coordinate
	 * @return Object position
	 */
	public Vector2f coordToObjectPos(GPoint coord) {
		return new Vector2f(
				(coord.x * Level.BLOCK_SIZE) + (Level.BLOCK_SIZE / 2),
				(coord.y * Level.BLOCK_SIZE) + (Level.BLOCK_SIZE / 2));
	}
	
	/** Returns objects by coordinate
	 * @param coord - Coordinate
	 * @return Array of object
	 */
	public GameObject[] getObjectsByCoord(GPoint coord) {
		List<GameObject> lstGameObjects = new ArrayList<GameObject>();
		
		for (GameObject gameObject : _lstObjects) {
			GPoint objectCoord = getObjectCoord(gameObject);
			if ((objectCoord.x == coord.x) && (objectCoord.y == coord.y))
				lstGameObjects.add(gameObject);
		}
		
		GPoint playerCoord = getObjectCoord(_player);
		if ((playerCoord.x == coord.x) && (playerCoord.y == coord.y))
			lstGameObjects.add(_player);
		
		// Cast to GameObject[]
		GameObject[] gameObjects = new GameObject[lstGameObjects.size()];
		for (int i = 0; i < lstGameObjects.size(); i++) {
			gameObjects[i] = lstGameObjects.get(i);
		}
		
		return gameObjects;
	}
	
	/** Returns objects by name
	 * @param name - Object name
	 * @return Array of object
	 */
	public GameObject[] getObjectsByName(String name) {
		List<GameObject> lstObjects = new ArrayList<GameObject>();
		for (GameObject gameObject : _lstObjects)
			if (gameObject.getName() == name) lstObjects.add(gameObject);
		
		// Cast to GameObject[]
		GameObject[] ret = new GameObject[lstObjects.size()];
		for (int i = 0; i < lstObjects.size(); i++)
			ret[i] = lstObjects.get(i);
		
		return ret;
	}
	
	/** Returns objects by class name
	 * @param className - Class name
	 * @return Array of object
	 */
	public GameObject[] getObjectsByClass(String className) {
		List<GameObject> lstObjects = new ArrayList<GameObject>();
		for (GameObject gameObject : _lstObjects)
			if (gameObject.getClass().getName() == className) lstObjects.add(gameObject);
		
		// Cast to GameObject[]
		GameObject[] ret = new GameObject[lstObjects.size()];
		for (int i = 0; i < lstObjects.size(); i++)
			ret[i] = lstObjects.get(i);
		
		//if (ret.length == 0) System.out.println("Warning: not found '" + className + "' objects");
		
		return ret;
	}
	
	/** Move object to point
	 * @param gameObject - Game object
	 * @param coord - Coordinate
	 * @return True if moving is complete
	 * @throws SlickException 
	 */
	public boolean moveTo(GameObject gameObject, GPoint coord) throws SlickException {
        boolean isMove = true;

        // Get level map
        MapBlock[][] mapBlocks = _level.getMap().getBlocks();
        
        GPoint objectCoord = getObjectCoord(gameObject);
        
        if (((objectCoord.x != coord.x) || (objectCoord.y != coord.y))
            && (mapBlocks[coord.x][coord.y].Type == EMapBlockType.Floor))
        {
            // Check collision
            GameObject[] otherObjects = getObjectsByCoord(coord);
            if (otherObjects.length > 0)
            {          	
            	for (GameObject otherObject : otherObjects) {
            		gameObject.onCollision(otherObject, true);
            		boolean m = otherObject.onCollision(gameObject, false);
            		if (isMove) isMove = m;
            	}
            }
            else isMove = true;

            // If move is allowed that replace specified object
            if (isMove)
                gameObject.Position.set(coordToObjectPos(coord));
        }

        return isMove;
	}
	
	/** Update object manager
	 * @param delta - Time delta
	 */
	public void update(int delta) throws SlickException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
		// Update camera
		_camera.update(delta);

		// Update objects
		for (GameObject gameObject : _lstObjects)
			gameObject.update(delta);
		
		// Sort objects by drawing layer
		for (int i = 0; i < _lstObjects.size(); i++) {
			if ((i - 1) >= 0) {
				if (_lstObjects.get(i).getLayerForDrawing() < _lstObjects.get(i - 1).getLayerForDrawing()) {
					// Swap objects
					Collections.swap(_lstObjects, i, i - 1);
					i = -1;
				}
			}
		}

		// Update player
		_player.update(delta);
	}
	
	/** Draw all objects
	 * @param g - Slick2d graphics
	 */
	public void draw(Graphics g) {
        // Draw objects
        for (GameObject gameObject : _lstObjects)
            gameObject.draw(g);

        // Draw player
        _player.draw(g);
	}
	
	/** Update step 
	 * @throws SlickException */
	public void step() throws SlickException {
		// Player step
		_player.step();

		// Camera step
		_camera.step();

		// Step for all objects
		for (GameObject gameObject : _lstObjects)
			gameObject.step();
		
		// Destroy enemy
        for (GameObject gameObject : _lstObjects) {
        	if (gameObject.isCreature()) {
        		GOCreature creature = (GOCreature) gameObject;
        		if (creature.isDied())
        			creature.destroy();
        	}
        }
        
		// Delete all destroyed objects
		for (int i = 0; i < _lstObjects.size(); i++) {
			if (_lstObjects.get(i).isDestroyed()) {
				_lstObjects.remove(i);
				i = 0;
			}
		}
	}
}
